﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Windows.ApplicationModel;

namespace CorpStoreServer
{
    public class PackageInfo
    {
        public string Name { get; set; }                                // name of package (e.g. KhanAcademy.KhanAcademy)
        public string FamilyName { get; set; }                          // family name of package (e.g. KhanAcademy.KhanAcademy_h7gxd2e83qjmg)
        public string CreateDate { get; set; }                          // date when package was created (e.g. 2014-02-17 21:04:40Z)
        public string InstalledLocation { get; set; }                   // location where package is installed on user's system (e.g. C:\\Program Files\\WindowsApps\\KhanAcademy.KhanAcademy_1.3.3.0_neutral__h7gxd2e83qjmg)
        public string AppxFn { get; set; }                              // full path + filename of *.appx package (e.g. c:\\appxPackages\\KhanAcademy.appx)
        public string StoreID { get; set; }                             // unique store ID for an app which exists in store (d23cc2b2-c105-4db3-9946-e44bacc56f7b)
        public string ExeName { get; set; }                             // name of win32 executable (if applicable)

        public string DisplayName { get; set; }                         // display name (e.g. Khan Academy)
        public string PublisherDisplayName { get; set; }                // publisher name (e.g. Khan Academy)
        public string Version { get; set; }                             // version number of package (if avail)
        public string Description { get; set; }                         // description of package (derived from commonCatalog or from windows store)
        public string GroupName { get; set; }                           // group name this package belongs to (derived from commonCatalog); grouping used within CorpStore app
        public string Identity { get; set; }                            // identity of app package used to launch app (e.g. App)
        public string SplashScreenFn { get; set; }                      // splash screen image filename (e.g. b9c446bb-9f30-40e4-9609-90475e3e6efb/AppTile.1.27558.28145.png)
        public string LogoFn { get; set; }                              // square logo image filename (e.g. b9c446bb-9f30-40e4-9609-90475e3e6efb/AppTile.2.27558.28145.png)
        public List<ScreenshotInfo> Screenshots { get; set; }           // list of screenshots associated with this package (either derived from commonCatalog or from windows store)
        public List<string> Features { get; set; }                      // list of features associated with this package (either derived from commonCatalog or from windows store)
        public string Args { get; set; }                                // win32 command line arguments (if this package is a win32 application)

        public bool IsFeatured { get; set; }                            // true if this package is to be featured within CorpStore on the flip view
        public bool NeedsUpdate { get; set; }                           // true if the package currently installed is out of date

        public PackageInfo()
        {
            Screenshots = new List<ScreenshotInfo>();
            Features = new List<string>();
        }

        /// <summary>
        /// This function attempts to define all internal properties based on the 
        /// contents of 'pkg'.  
        /// </summary>
        /// <param name="pkg">Windows.ApplicationModel.Package object</param>
        /// <param name="errStr">error string if any problems</param>
        /// <returns>true if all properties in this class were initialized correctly</returns>
        public bool Init(Package pkg, out string errStr)
        {
            bool blnOK = false;

            try
            {
                // load xml document from manifest file
                string manifestFn = getPackageInstalledLocation(pkg, out errStr);
                if (!string.IsNullOrEmpty(manifestFn))
                {
                    manifestFn += @"\appxmanifest.xml";
                    XmlDocument xml = new XmlDocument();
                    xml.Load(manifestFn);

                    Name = pkg.Id.Name;
                    FamilyName = pkg.Id.FamilyName;
                    InstalledLocation = pkg.InstalledLocation.Path;
                    CreateDate = pkg.InstalledLocation.DateCreated.ToString("u");
                    DisplayName = GenLib.GetManifestElementValue(pkg, xml, "DisplayName", "", out errStr);
                    PublisherDisplayName = GenLib.GetManifestElementValue(pkg, xml, "PublisherDisplayName", "", out errStr);
                    Version = GenLib.parseElement(xml, "Identity", "Version", out errStr);

                    string descrip = GenLib.GetManifestElementValue(pkg, xml, "VisualElements", "Description", out errStr);
                    GroupName = extractGroupNameFromDescription(ref descrip); 
                    Description = descrip;

                    Identity = GenLib.parseElement(xml, "Application", "Id", out errStr);
                    SplashScreenFn = GetFnWithScale(InstalledLocation, GenLib.GetManifestElementValue(pkg, xml, "SplashScreen", "Image", out errStr));
                    LogoFn = GetFnWithScale(InstalledLocation, GenLib.GetManifestElementValue(pkg, xml, "Logo", "", out errStr));

                    // search within the appx path for any files called 'screenshot.png'
                    string fn = FindScreenshotFn(InstalledLocation);
                    if (!string.IsNullOrEmpty(fn))
                        Screenshots.Add(new ScreenshotInfo() { Filename = fn, Description = "Screenshot" });
                    else
                        Screenshots.Add(new ScreenshotInfo() { Filename = SplashScreenFn, Description = "No screenshot available" });

                    // no way to get features from an installed app - just set to 'Unknown'
                    Features.Add("Unknown");
                    blnOK = true;
                }
                else
                {
                    // package location cannot be determined - just set basic values
                    errStr = string.Format("Package installed location is undefined - skipping package '{0}' : {1}", pkg.Id.Name, errStr);
                    blnOK = false;
                }
            }
            catch (Exception ex)
            {
                errStr = string.Format("Unhandled exception while extracting logo fn from manifest: {0}", ex.Message);
                blnOK = false;
            }

            return blnOK;
        }

        /// <summary>
        /// returns the package location
        /// </summary>
        /// <param name="pkg"></param>
        /// <param name="errStr"></param>
        /// <returns></returns>
        private string getPackageInstalledLocation(Package pkg, out string errStr)
        {
            string location = "";

            errStr = "";
            try
            {
                location = pkg.InstalledLocation.Path;
            }
            catch (Exception ex)
            {
                errStr = string.Format("Package '{0}' does not have an installed location!: {1}", pkg.Id.Name, ex.Message);
            }

            return location;
        }


        /// <summary>
        /// Adds a single screenshot to the internal list of screenshots of this package
        /// </summary>
        /// <param name="filename">path+filename of screenshot image filename</param>
        /// <param name="description">text describing image which will appear immediately below image as a caption</param>
        public void AddScreenshot(string filename, string description)
        {
            if (Screenshots == null) Screenshots = new List<ScreenshotInfo>();
            Screenshots.Add(new ScreenshotInfo(filename, description));
        }


        /// <summary>
        /// If 'descrip' contains a square open and close bracket, the contents
        /// within those brackets is considered to be the group name.  If no
        /// brackets are found, this function sets groupName to 'Other'.
        /// </summary>
        /// <param name="descrip">package description</param>
        /// <returns>group name extracted from description</returns>
        private string extractGroupNameFromDescription(ref string descrip)
        {
            string groupName = "Other";

            if (descrip.StartsWith("[") && descrip.IndexOf(']') != -1)
            {
                // group name included in brackets preceding description - parse it out
                groupName = descrip.Substring(1, descrip.IndexOf("]") - 1);
                descrip = descrip.Substring(descrip.IndexOf("]") + 1);
            }

            return groupName;
        }


        /// <summary>
        /// This function attempts to determine the name of an image file which 
        /// could have a dpi suffix.  This function exists since image filenames
        /// contained in an app manifest typically do not contain a DPI suffix,
        /// but the actual file does.  
        /// 
        /// If the filename as it is defined in the appxmanifest (origFn) does 
        /// not exist, this function appends '.scale-100' to the filename prefix
        /// and checks if that exists.  If it does, this function will return
        /// the filename with the '.scale-100' suffix.  If the original filename
        /// existed, that origFn will be returned unmodified.
        /// </summary>
        /// <param name="appxPath">path to where the app has been installed</param>
        /// <param name="origFn">original image filename</param>
        /// <returns>either the original image filename as-is or one with a '.scale-100' suffix if that exists</returns>
        public string GetFnWithScale(string appxPath, string origFn)
        {
            if (!string.IsNullOrEmpty(appxPath) && !string.IsNullOrEmpty(origFn) && !File.Exists(string.Format(@"{0}\{1}", appxPath, origFn)))
            {
                // could not find file - append ".scale-100" to filename and check if that exists
                string testFn = string.Format(@"{0}\{1}\{2}.scale-100{3}", appxPath, Path.GetDirectoryName(origFn), Path.GetFileNameWithoutExtension(origFn), Path.GetExtension(origFn));
                if (File.Exists(testFn))
                    origFn = testFn.Replace(appxPath + "\\", "");
                else
                    origFn = "";
            }
            return origFn;
        }


        /// <summary>
        /// Attempts to find a file with the name 'screenshot.png' in the directory and
        /// all subdirectories of 'appxPath'.  This function exists as a last ditch 
        /// effort to find a screenshot image for a package if no screenshots were 
        /// explicitly defined.
        /// </summary>
        /// <param name="appxPath">root path to the package installation folder</param>
        /// <returns>empty string if no screenshot file can be found or the relative path to 'screenshot.png'</returns>
        public string FindScreenshotFn(string appxPath)
        {
            string fn = "";

            var files = System.IO.Directory.EnumerateFiles(appxPath, "screenshot.png", System.IO.SearchOption.AllDirectories);
            if (files != null && files.Count() > 0)
            {
                // found our screenshot file - return it
                fn = files.ElementAt(0).Replace(appxPath + "\\", ""); 
            }

            return fn;
        }


        /// <summary>
        /// Returns the app type of this package based on which properties have been defined.
        /// If the StoreID is defined, this function assumes the package is a store app.
        /// If ExeName is defined, must be a win32 application.
        /// Else must be an enterprise store app.
        /// </summary>
        /// <returns>the type of app/application of this package</returns>
        public eAppType AppType()
        {
            eAppType myType = eAppType.unknown;
            if (!string.IsNullOrEmpty(StoreID))
                myType = eAppType.storeApp;
            else if (!string.IsNullOrEmpty(ExeName))
                myType = eAppType.win32App;
            else
                myType = eAppType.enterpriseApp;

            return myType;
        }

    }


    /// <summary>
    /// all possible application types supported by CorpStore.  This list can
    /// be augmented to support other types of applications as needed.
    /// </summary>
    public enum eAppType
    {
        storeApp,
        enterpriseApp,
        win32App,
        unknown,
    }


}
